/** @file    PlayerGStreamer010.cpp
 *  @author  Alessandro Polo
 *  @version $Id: PlayerGStreamer010.cpp 2825 2010-08-01 16:08:00Z alex $
 *  @brief
 * File containing methods for the PlayerGStreamer010 class.
 * The header for this class can be found in PlayerGStreamer010.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include "PlayerGStreamer010.h"
 #include "IPlayerGStreamerListener.h"
 #include <interfaces/devices/PlayerAudio.h>

 #include <core/FileSystem.h>
 #include <fstream> // m3u parsing
 #include <iostream> // m3u parsing
 #include <algorithm> // folder parsing (sorting)


using namespace std;
using namespace wosh;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
{
	GMainLoop *gst_loop = (GMainLoop *) data;
	(void)gst_loop; (void)bus;
	if ( msg == NULL ) return TRUE;
	// get the associated PlayerGStreamer010 instance (from map<bus,instance>)
	MutexLockerRead mL(&PlayerGStreamer010::instancesMux);
	PlayerGStreamer010* instance = NULL;
	if ( PlayerGStreamer010::instances.find(gst_loop) != PlayerGStreamer010::instances.end() )
		instance = PlayerGStreamer010::instances[gst_loop];
	if ( instance == NULL ) {
		return TRUE;
	 }
	switch (GST_MESSAGE_TYPE (msg)) {
		// analyze the new message
		case GST_MESSAGE_EOS: { // end-of-stream reached in a pipeline
				// media file finished
				instance->item_EndOfStream();
				break;
			 }
		case GST_MESSAGE_BUFFERING: {
				// update buffering information
				gint percent = 0;
				gst_message_parse_buffering(msg, &percent);
				instance->item_Buffering( percent );
				break;
			 }
		case GST_MESSAGE_STREAM_STATUS: { // status about a stream, emitted when it starts, stops, errors, etc..
				// gst_message_parse_stream_status(GstMessage *message, GstStreamStatusType *type, GstElement **owner);
				break;
			 }
		case GST_MESSAGE_DURATION: { // duration of a pipeline changed
				// BUG, SEEMS THAT MESSAGE IS NOT ALWAYS RAISED, WE FORCE CALL IN gst_StateChanged()	
				// update current item's duration
				//gint64 len = 0;
				//GstFormat fmt = GST_FORMAT_TIME; // time in nanoseconds 
				//gst_message_parse_duration(msg, &fmt, &len);
				//instance->item_Duration( (int64)len );
				break;
			 }
		case GST_MESSAGE_STATE_CHANGED: {
				// pipeline changed statis, let's forward to instance
				GstState oldState;
				GstState newState;
				GstState pending;
				gst_message_parse_state_changed(msg, &oldState, &newState, &pending);
				instance->gst_StateChanged( newState, oldState );
				break;
			 }
		case GST_MESSAGE_TAG: { // a tag was found
				GstTagList *taglist;
				gst_message_parse_tag(msg, &taglist);
				std::map<string, string> fields;
				char* data = NULL;
				if (gst_tag_list_get_string(taglist, GST_TAG_TITLE, &data) && data)
					fields[GST_TAG_TITLE] = string(data);
				data = NULL;
				if (gst_tag_list_get_string(taglist, GST_TAG_ARTIST, &data) && data)
					fields[GST_TAG_ARTIST] = string(data);
				data = NULL;
				if (gst_tag_list_get_string(taglist, GST_TAG_ALBUM, &data) && data)
					fields[GST_TAG_ALBUM] = string(data);
				data = NULL;
			//	if (gst_tag_list_get_string(taglist, GST_TAG_GENRE, &data) && data)
			//		fields[GST_TAG_GENRE] = string(data);
			//	data = NULL;
			//	if (gst_tag_list_get_string(taglist, GST_TAG_BITRATE, &data) && data)
			//		fields[GST_TAG_BITRATE] = string(data);
				gst_tag_list_free(taglist);
				instance->item_InfoTag(fields);
				break;
			 }
		case GST_MESSAGE_INFO: {
				// information raised, let's forward to instance
				gchar  *debug;
				GError *error;
				gst_message_parse_info(msg, &error, &debug);
				g_free (debug);
				instance->gst_Info( error->message );
				g_error_free (error);
				break;
			 }
		case GST_MESSAGE_WARNING: {
				// warning raised, let's forward to instance
				gchar  *debug;
				GError *error;
				gst_message_parse_warning(msg, &error, &debug);
				g_free (debug);
				instance->gst_Warning( error->message );
				g_error_free (error);
				break;
			 }
		case GST_MESSAGE_ERROR: {
				// error raised, let's forward to instance
				gchar  *debug;
				GError *error;
				gst_message_parse_error(msg, &error, &debug);
				g_free (debug);
				instance->gst_Error( error->message );
				g_error_free (error);
				break;
			 }
		default:
			break;
	 }
	return TRUE;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::map<GMainLoop*, PlayerGStreamer010*> PlayerGStreamer010::instances; ///< GLib loop<->instance associations
MutexRW PlayerGStreamer010::instancesMux; ///< mutex for accessing PlayerGStreamer010::instances 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

PlayerGStreamer010::PlayerGStreamer010( BundleGeneric& bundle )
	: PlayerGStreamer(bundle) {
	this->gst_pipeline = NULL;

	this->gst_sink_audio = NULL;
	this->gst_bin_play = NULL;

	this->gst_state = GST_STATE_NULL;

	this->device_sink = "alsasink";
	this->device_output = "";

	this->currentRepeat = 0;
	this->playListRepeat = 0;

	this->volume_muted = 0;
	this->volume_init = 30;

	// init only once, there may be more Players on same host
	PlayerGStreamer010::instancesMux.lockForRead();
	if ( PlayerGStreamer010::instances.size() == 0 )
		gst_init(NULL, NULL);
	PlayerGStreamer010::instancesMux.unLock();

	// register the player and its gst loop (for the callbacks)
	PlayerGStreamer010::instancesMux.lockForWrite();
	this->gst_loop = g_main_loop_new(NULL, false);
	PlayerGStreamer010::instances[this->gst_loop] = this;
	PlayerGStreamer010::instancesMux.unLock();
 }

PlayerGStreamer010::~PlayerGStreamer010() {
	Log(LOG_VERBOSE, ":~PlayerGStreamer010(): Destroying.." );
	if ( isThreadRunning() )
		quitThread(20000);

	// release playlist items
	this->playlist.clear();

	PlayerGStreamer010::instancesMux.lockForWrite();
	// remove current instance from global list
	PlayerGStreamer010::instances[this->gst_loop] = NULL;
	map<GMainLoop*, PlayerGStreamer010*>::iterator it;
	it = PlayerGStreamer010::instances.find(this->gst_loop);
	if ( it != PlayerGStreamer010::instances.end() ) {
		PlayerGStreamer010::instances.erase (it);
	 }
	// de-init GST only when this instance is the last active
	if ( PlayerGStreamer010::instances.size() == 0 ) {
		Log(LOG_INFO, ":~PlayerGStreamer010(): De-Initializing GST.." );
		gst_deinit();
	 }
	PlayerGStreamer010::instancesMux.unLock();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN

bool PlayerGStreamer010::initializingThread() {
	Log(LOG_DEBUG, ":initializingThread(): STARTING" );

	// create the pipeline object (main object of GStreamer lib)
	this->gst_pipeline = gst_pipeline_new("gst_pipeline");

	// link the GLib bus with GStreamer
	GstBus * bus = gst_pipeline_get_bus(GST_PIPELINE (this->gst_pipeline));
	gst_bus_add_watch(bus, bus_call, this->gst_loop);
	gst_object_unref(bus);

	// create the PLAYBIN component, which will implement loading/decoding/playing logic
	if ( GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 10 && GST_VERSION_MICRO < 26 ) {
		Log(LOG_DEBUG, ":initializingThread(): Using pluging 'playbin'" );
		this->gst_bin_play = gst_element_factory_make( "playbin", "music-player" );
	 }
	else {
		Log(LOG_DEBUG, ":initializingThread(): Using pluging 'playbin2'" );
		this->gst_bin_play = gst_element_factory_make( "playbin2", "music-player" );
	 }
	if ( this->volume_init != 0 ) {
		gdouble vol = (gdouble)this->volume_init / 100.0;
		g_object_set(G_OBJECT (this->gst_bin_play), "volume", vol, NULL);
	 }
	//g_object_set (G_OBJECT (this->gst_bin_play), "uri", this->file.c_str(), NULL);

	// force ALSA module output
	this->gst_sink_audio = gst_element_factory_make( this->device_sink.c_str(), "sink-audio");
	if ( this->device_output != "" )
		g_object_set(G_OBJECT (this->gst_sink_audio), "device", this->device_output.c_str(), NULL);
	g_object_set(G_OBJECT (this->gst_bin_play), "audio-sink", gst_sink_audio, NULL);

	//sink_video = gst_element_factory_make ("fakesink", "sink-fake");
	//g_object_set (G_OBJECT (this->gst_bin_play), "video-sink", sink_video, NULL);

	// finally add the PLAYBIN component to the pipeline
	gst_bin_add(GST_BIN (this->gst_pipeline), this->gst_bin_play);
	return true;
 }
/*
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-alsamixer.html
http://www.mail-archive.com/gtkmm-list@gnome.org/msg13985.html
-
void fadeInSynch() {
}
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-multifilesrc.html
audiotestsrc
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-bad-plugins/html/gst-plugins-bad-plugins-dtmfsrc.html

*/


void PlayerGStreamer010::runThread() {
	Log(LOG_DEBUG, ":runThread(): RUNNING" );

	// let's tell listener we are almost ready
	if ( this->playerListener != NULL )
		this->playerListener->changed_PlayState( _Player_STATE_Idle, this );

	// just enter the GLib loop, it will block thread till g_main_quit is called
	g_main_loop_run(this->gst_loop);
 }

void PlayerGStreamer010::exitingThread() {
	Log(LOG_DEBUG, ":exitingThread(): EXITING" );

	gst_element_set_state (this->gst_pipeline, GST_STATE_NULL);

	// unlink pipeline
	gst_object_unref(GST_OBJECT (this->gst_pipeline));

	// ensure components won't be accessed by local functions
	this->gst_sink_audio = NULL;
	this->gst_bin_play = NULL;
	this->gst_pipeline = NULL;
 }

WRESULT PlayerGStreamer010::quitThread( unsigned long timeout ) {
	if ( !isThreadRunning() ) {
		Log(LOG_WARNING, ":quitThread() Thread was already stopped!" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_DEBUG, ":quitThread()" );

	// when playing.. let's stop stream first
	if ( isPlaying() )
		stop();

	// exit the GST loop (GLib)
	g_main_quit(this->gst_loop);

	// ensure Thread is stopped, not really required
	return ThreadImpl::quitThread(timeout);
 }

////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GSTREAMER EVENTS

void PlayerGStreamer010::gst_StateChanged( GstState new_state, GstState old_state ) {
	(void)old_state;
	// let's not flood the log, write only once
	if ( new_state != this->gst_state || Log.isDebug() ) {
		if ( Log.isDebug() || new_state == GST_STATE_PLAYING || new_state == GST_STATE_PAUSED )
			Log(LOG_VERBOSE, ":gst_StateChanged() : %s (%s) => %s", PlayerGStreamer010::getGstState2String(this->gst_state),
																	PlayerGStreamer010::getGstState2String(old_state),
																	PlayerGStreamer010::getGstState2String(new_state) );
	 }
	this->gst_state = new_state;
	if ( this->playerListener == NULL ) return;
	// so we will raise events for the listener
	switch(new_state) {
		case GST_STATE_PLAYING: {
				// since the 
				if ( this->playerListener != NULL )
					this->playerListener->changed_PlayState( _Player_STATE_Playing, this );
				item_Duration(-2); // force query
				break;
			 }
		case GST_STATE_READY:
		case GST_STATE_PAUSED:
		case GST_STATE_NULL:
		default: {
				// tell the listener we are NOT playing
				if ( this->playerListener != NULL )
					this->playerListener->changed_PlayState( _Player_STATE_Idle, this );
				break;
			 }
	 }
 }

void PlayerGStreamer010::item_InfoTag( const std::map<std::string, std::string> &fields ) {
	// some tags are available, 
	this->playlist.getTracks().transactionBeginWrite();
	MediaTrack* cItem = this->playlist.getCurrentTrack();
	if ( cItem == NULL ) {
		this->playlist.getTracks().transactionEnd();
		Log(LOG_WARNING, ":item_InfoTag() : Can't access current item (while playing)!" );
	 }
	std::map<std::string,std::string>::const_iterator it;
	for ( it=fields.begin(); it!=fields.end(); it++ ) {
		Log(LOG_VERBOSE, ":item_InfoTag() : '%s' = '%s'", it->first.c_str(), it->second.c_str() );
		if ( it->first == "title" ) cItem->setTitle(it->second);
		if ( it->first == "artist" ) cItem->setArtist(it->second);
		if ( it->first == "album" ) cItem->setAlbum(it->second);
	 }
	this->playlist.getTracks().transactionEnd();
 }

void PlayerGStreamer010::item_Buffering( int percent ) {
	Log(LOG_INFO, ":item_Buffering() : Buffering %d percent done..", percent );
 }

void PlayerGStreamer010::item_Duration( int64 nanoseconds ) {
	Log(LOG_DEBUG, ":item_Duration() : Duration %"PRId64" ns..", nanoseconds );
	this->playlist.getTracks().transactionBeginWrite();
	MediaTrack* cItem = this->playlist.getCurrentTrack();
	if ( cItem == NULL ) {
		this->playlist.getTracks().transactionEnd();
		Log(LOG_WARNING, ":item_Duration() : Can't access current item (while playing)!" );
		return;
	 }
/*
	// since file is playing.. it was loaded and info are available, lets update MediaTrack object
	gint64 len_ns = 1;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( gst_element_query_duration( this->gst_pipeline, &fmt, &len_ns) ) {
		char duration[256]; memset(duration, 0, 256);
		sprintf(duration, "%" GST_TIME_FORMAT, GST_TIME_ARGS (len_ns));
		cItem->getProperties().update( "Duration", string(duration) );
		cItem->getProperties().update( _MediaTrack_KEY_Length, Variant_TS((int64)len_ns/1000) );
		Log(LOG_VERBOSE, ":item_Duration() : Duration %s [%"PRId64"]", duration, (int64)len_ns );
	 }
	else
		Log(LOG_WARNING, ":item_Duration() : FAILED calling gst_element_query_duration!" );
*/
	if ( this->playerListener != NULL )
		this->playerListener->changed_PlayItem( cItem, this );
	this->playlist.getTracks().transactionEnd();

 }

void PlayerGStreamer010::item_EndOfStream() {
	// current media just finished.. 
	// repeat current file?
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());

	if ( this->currentRepeat ) {
		Log(LOG_INFO, ":item_EndOfStream() Repeating.." );
		if ( this->playlist.getCurrentIndex() >= 0 && this->playlist.getTracks().size() > 0 )
			play_index_( this->playlist.getTracks().size(), false );
		return;
	 }

	if ( this->playlist.getCurrentIndex() < 0 || this->playlist.getTracks().size() == 0 ) {
		Log(LOG_INFO, ":item_EndOfStream()" );
		return;
	 }
	// so: playListIndex is validated
	// did we reach end of playlist?
	if ( this->playlist.getCurrentIndex() == (int)this->playlist.getTracks().size() -1 ) {
		Log(LOG_INFO, ":item_EndOfStream() : End of Playlist" );
		// should we start again the playlist?
		if ( this->playListRepeat == 0 )
			return;
		play_index_(0, false, false );
		return;
	 }
	Log(LOG_INFO, ":item_EndOfStream(%d) Playing next Item [of %d]", this->playlist.getCurrentIndex(), this->playlist.getTracks().size() );
	// ok..then play next item!
	play_index_( this->playlist.getCurrentIndex() + 1, true, false );
	return;
 }

void PlayerGStreamer010::gst_Info( const char* message ) {
	// better to log this info
	if ( message != NULL )
		Log(LOG_VERBOSE, ":gst_Info() : %s", message );
	else
		Log(LOG_VERBOSE, ":gst_Info() : NULL" );
 }

void PlayerGStreamer010::gst_Warning( const char* message ) {
	// better to log this info
	if ( message != NULL )
		Log(LOG_WARNING, ":gst_Warning() : %s", message );
	else
		Log(LOG_WARNING, ":gst_Warning() : NULL" );
 }

void PlayerGStreamer010::gst_Error( const char* message ) {
	// this is a critical error
	// app should stop playback of the pipeline and not assume that more data will be played. 
	if ( message != NULL )
		Log(LOG_CRITICAL, ":gst_Error() : %s", message );
	else
		Log(LOG_CRITICAL, ":gst_Error() : NULL" );
	if ( this->gst_pipeline == NULL ) return;
	Log(LOG_DEBUG, ":gst_Error() : Stopping playback.." );
	gst_element_set_state(this->gst_pipeline, GST_STATE_NULL );
	if ( !this->playlist.isLastTrack() ) {
		Log(LOG_VERBOSE, ":gst_Error() : Resuming Next [%d]..", this->playlist.getCurrentIndex() );
		next();
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////// GSTREAMER EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////// INTERNAL ENQUEUE/PLAY

int PlayerGStreamer010::enqueue_( const std::string& file_url ) {
	// algorithm:
	//  1. fix the url
	//  2. parse it {may be invalid, folder, .m3u playlist}
	//  3. enqueue item(s)
	//  4. return playindex of first item or (-)ERROR#
	if ( file_url.size() < 3 ) {
		Log(LOG_CRITICAL, ":enqueue_(%s) : INVALID File!", file_url.c_str() );
		return -1;
	 }
	// protocol separator (file://path/to/resource; dvd://1; ..)
	ResourcePath res_uri(file_url);
	bool isLocalResource = res_uri.isFile();
	string url_resolved = res_uri.resolve(true); // omit protocol
	Log(LOG_VERBOSE, ":enqueue_(%s) : Parsed[%d] to %s", file_url.c_str(), isLocalResource, url_resolved.c_str() );
	// get the associated index in the playlist
	int element_index = this->playlist.getTracks().size();
	// is it a file (audio or playlist) or a folder ?
	if ( isLocalResource )	 {
		// is the resource.,. a FOLDER? we are kind.. and will add its files :)
		if ( FilePath::isFolder(url_resolved) ) {
			// inline creation of playlist, get files list
			vector<string> files;
			WRESULT ret = FileSystem::getFilesOf(url_resolved, files, false, false ); // NOT recursive, only filename
			if ( WFAILED(ret) || files.size() == 0 ) {
				Log(LOG_CRITICAL, ":enqueue_(%s): INVALID Folder [%d files]", url_resolved.c_str(), files.size() );
				return -2;
			 }
			// sort alphabetically
			std::sort( files.begin(), files.end() );
			Log(LOG_VERBOSE, ":enqueue_(%s): Analysing %d elements from folder", url_resolved.c_str(), files.size() );
			// enqueue each element [recursive call]
			std::vector<string>::const_iterator it;
			for ( it=files.begin(); it!=files.end(); it++ ) {
				enqueue_( FilePath::combine(res_uri.getPath(), *it) ); // recursive
			 }
			// anyway return the index of first added element
			return element_index;
		 }
		// is the resource a playlist file (M3U, Winamp)
		else if ( FilePath::isFile(url_resolved) && FilePath::getExtension(url_resolved, 1) == "M3U" ) {
			// open the playlist (text) file for read
			ifstream infile;
			infile.open( url_resolved.c_str(), ifstream::in );
			if ( !infile.is_open() ) {
				Log(LOG_CRITICAL, ":enqueue_(%s) : Invalid filename (can't open)", url_resolved.c_str() );
				return WRET_ERR_PARAM;
			 }
			// file is opened, let's parse contents and enqueue items
			Log(LOG_VERBOSE, ":enqueue_(%s): Loading Playlist..", url_resolved.c_str() );
 			// load the current system path, since media-paths are relative to current folder
			string base_dir = FilePath::getFolder( url_resolved );
			string buffer; size_t cPos; long found = 0;
			while ( infile.good() ) { // walk the file
				if ( infile.eof() ) break;
				getline( infile, buffer);
				// ignore comments, info and stuff
				cPos = buffer.find("#");
				if ( cPos != string::npos ) continue;
				if ( buffer.size() < 3 ) continue;
				Utilities::trimSpaces(buffer);
				Utilities::trimEndLine(buffer);
				buffer = FilePath::combine( base_dir, buffer );
				FilePath::toLocalPath(buffer);
				// we have a file url in the buffer, enqueue it (recursive call)
				int index = enqueue_( buffer );
				if ( index >= 0 )
					++found;
			 }
			Log(LOG_INFO, ":enqueue_(%s) : Loaded %d files from %s", res_uri.getPath().c_str(), found, url_resolved.c_str() );
			// we are done :)
			infile.close();
			return element_index; // return the index of first added element
		 }
		else if ( !FilePath::isFile(url_resolved) ) { // an invalid filesystem URI!?
			Log(LOG_CRITICAL, ":enqueue_(%s) : Local file '%s' was not found!", res_uri.getPath().c_str(), url_resolved.c_str() );
			return -3;
		 }
		if ( !isValidFile(url_resolved) ) { // an invalid resource?
			Log(LOG_CRITICAL, ":enqueue_(%s) : Local file '%s' is not a valid resource!", res_uri.getPath().c_str(), url_resolved.c_str() );
			return -4;
		 }
	 }
	// then it's a valid file/url, this is often called (recursive) by previous blocks
	// create a new item
	MediaTrack* item = new MediaTrack(res_uri);
	item->setIndex(element_index);
	item->getProperties().set(_MediaTrack_KEY_SizeKb, FileSystem::getSizeOf(url_resolved) / 1024 );
	this->playlist.getTracks().push_back(item);

	Log(LOG_INFO, ":enqueue_(%s) : Enqueued #%d [%d]", res_uri.getPath().c_str(), element_index, this->playlist.getTracks().size()-1 );
	// return the index of last element in the playlist (just added it)
	return element_index;
 }

bool PlayerGStreamer010::isValidFile( const std::string& ) const {
	return true;
 }

WRESULT PlayerGStreamer010::play_index_( unsigned int index, bool synch, bool resume_next ) {
	// validate input
	if ( this->playlist.getTracks().size() == 0 ) {
		Log(LOG_CRITICAL, ":play_(%ud) FAILED : Empty Playlist!", index );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( (int)index >= this->playlist.getTracks().size() ) {
		Log(LOG_CRITICAL, ":play_() FAILED : %ud Out of (Playlist) Range!", index );
		return WRET_ERR_PARAM;
	 }
	// update current index and get the associated Item
	this->playlist.setCurrentIndex(index);
	MediaTrack* item = this->playlist.getCurrentTrack();
	if ( item == NULL ) {
		Log(LOG_CRITICAL, ":play_() FAILED : Item#%ud not found!", index );
		if ( resume_next )
			return play_index_(index+1, synch, resume_next);
		return WRET_ERR_INTERNAL;
	 }
	// finally play the item
	WRESULT ret = play_( item, synch );
	if ( WSUCCEEDED(ret) )
		return WRET_OK;
	if ( !resume_next )
		return WRET_ERR_INTERNAL;
	return play_index_(index+1, synch, resume_next);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::play_( wosh::entertainment::MediaTrack* item, bool  ) {
	// validate parameter and current status
	if ( item == NULL ) return WRET_ERR_PARAM;
	if ( this->gst_bin_play == NULL ) {
		Log(LOG_CRITICAL, ":play_(): NULL gst_bin_play" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":play_(): NULL gst_pipeline" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	// called while still playing, let's stop the media
	if ( isPlaying() ) {
		Log(LOG_VERBOSE, ":play_(%s): was playing, pausing..", item->getTrack().getPath().c_str() );
		gst_element_set_state(this->gst_pipeline, GST_STATE_NULL );
		if ( GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 10 && GST_VERSION_MICRO <= 19 ) {
			// GSTREAMER 0.10.19 WORKAROUND
			waitGstState( &this->gst_state, GST_STATE_NULL, 1000 );
			waitGstState( &this->gst_state, GST_STATE_READY, 1000 );
		 }
		//ThreadImpl::sleepForMSec(30);
	 }
	string url_resolved = item->getTrack().resolve(false); // keep protocol (http://, ..)
	if ( url_resolved.empty() || item->getTrack().isFile() ) {
		string pathref = item->getTrack().resolve(true); // omit protocol (file://)
		if ( !FilePath::exists(pathref) ) {
			Log(LOG_CRITICAL, ":play(): File %s not found, resolved %s", item->getTrack().getPath().c_str(), pathref.c_str() );
			return WRET_ERR_PARAM;
		 }
	 }
	Log(LOG_INFO, ":play_(%s): Playing resolved %s", item->getTrack().getPath().c_str(), url_resolved.c_str() );
	// update the GST object
	g_object_set(G_OBJECT (this->gst_bin_play), "uri", url_resolved.c_str(), NULL);
	gst_element_set_state(this->gst_pipeline, GST_STATE_PLAYING );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::play( const std::string& file_url ) {
	if ( file_url.empty() ) {
		// GST asynch
		// validate gst status
		if ( this->gst_pipeline == NULL ) {
			Log(LOG_CRITICAL, ":play(""): NULL gst_pipeline" );
			return WRET_ERR_ILLEGAL_USE;
		 }
		if ( isPlaying() ) {
			Log(LOG_CRITICAL, ":play(""): already playing!" );
			return WRET_ERR_ILLEGAL_USE;
		 }
		Log(LOG_VERBOSE, ":play(): setting PLAYING state.." );
		// set GST state, listeners may be informed asynch (see gst_StateChanged)
		gst_element_set_state(this->gst_pipeline, GST_STATE_PLAYING );
		return WRET_OK;
	 }
	// we enqueue file and play returned index (a negative value means an error)
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	int index = enqueue_(file_url);
	if ( index < 0 ) {
		Log(LOG_CRITICAL, ":play(%s): Invalid Item", file_url.c_str() );
		return WRET_ERR_PARAM;
	 }
	return play_index_(index, false);
 }

/////////////////////////////////////////////////////////////////////////////////////// INTERNAL ENQUEUE/PLAY
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PLAYER METHODS

WRESULT PlayerGStreamer010::enqueue( const std::string& file_url ) {
	// just masquerade the 'mutex-free' implementation
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	int index = enqueue_(file_url);
	if ( index < 0 )
		return WRET_ERR_INTERNAL;

	if ( this->playerListener != NULL )
		this->playerListener->changed_PlayList( &this->playlist, this );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::clear() {
	// clear playlist, releasing objects
	this->playlist.clear();
	Log(LOG_INFO, ":clear() Playlist cleaned." );

	if ( this->playerListener != NULL )
		this->playerListener->changed_PlayList( &this->playlist, this );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::first() {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	return play_index_( 0, false );
 }

WRESULT PlayerGStreamer010::last() {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	return play_index_( this->playlist.getTracks().size() - 1, false );
 }

WRESULT PlayerGStreamer010::prev() {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	if ( this->playlist.getCurrentIndex() < 1 )
		return WRET_ERR_ILLEGAL_USE;
	return play_index_( this->playlist.getCurrentIndex() - 1, false );
 }

WRESULT PlayerGStreamer010::next() {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	return play_index_( this->playlist.getCurrentIndex() + 1, false );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::remove( unsigned int index ) {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	this->playlist.remove(index);

	if ( this->playerListener != NULL )
		this->playerListener->changed_PlayList( &this->playlist, this );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::play( unsigned int index ) {
	MutexLockerWrite mL(this->playlist.getTracks().getMutex());
	return play_index_( index, false );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::pause() {
	// GST asynch (NULL GST_STATE_PAUSED)
	// validate gst status
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":pause(): NULL gst_pipeline" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( !isPlaying() ) {
		Log(LOG_CRITICAL, ":pause(): NOT playing" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_VERBOSE, ":pause(): setting PAUSED state.." );
	// set GST state, listeners may be informed asynch (see gst_StateChanged)
	gst_element_set_state(this->gst_pipeline, GST_STATE_PAUSED );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::stop() {
	// asynch PAUSE, asynch GST (NULL state)
	// validate gst status
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":stop(): NULL gst_pipeline" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( isPlaying() ) {
		gst_element_set_state(this->gst_pipeline, GST_STATE_PAUSED );
	 }
	else Log(LOG_WARNING, ":stop(): NOT playing" );
	// anyway let's try to set the state to IDLE
	gst_element_set_state(this->gst_pipeline, GST_STATE_NULL );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool PlayerGStreamer010::isPlaying() {
	// synch get GST state
	GstState state;
	GstStateChangeReturn ret = gst_element_get_state(this->gst_pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
	if ( ret == GST_STATE_CHANGE_FAILURE ) // can't load state!
		return false;
	return (state == GST_STATE_PLAYING);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool PlayerGStreamer010::gst_change_state_synch( GstState apply_gst_state, unsigned long timeout_ms ) {
	if ( this->gst_pipeline == NULL ) return false;
	Log(LOG_VERBOSE, ":gst_change_state_synch(): setting %s state [current %s]..", PlayerGStreamer010::getGstState2String(apply_gst_state), PlayerGStreamer010::getGstState2String(this->gst_state) );
	// set the state
	gst_element_set_state(this->gst_pipeline, apply_gst_state );
	// wait for the state to be applied, asynch GST callback is handled by gst_StateChanged
	bool ok = waitGstState( &this->gst_state, apply_gst_state, timeout_ms );
	if ( !ok )
		Log(LOG_WARNING, ":gst_change_state_synch(): Timedout setting state %s [current %s]..", PlayerGStreamer010::getGstState2String(apply_gst_state), PlayerGStreamer010::getGstState2String(this->gst_state) );
	return ok;
 }

////////////////////////////////////////////////////////////////////////////////////////////// PLAYER METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// VOLUME CONTROL

WRESULT PlayerGStreamer010::setVolume( int newVolume ) {
	if ( newVolume < 0 || newVolume > 100 ) {
		Log(LOG_CRITICAL, ":setVolume(): value %d not in range [0,100]", newVolume );
		return WRET_ERR_PARAM;
	 }
	// validate our status
	if ( !isThreadRunning() ) {
		// we will have to wait till thread is running to apply the new setting!
		// save the output (name) to local var
		this->volume_init = newVolume;
		Log(LOG_VERBOSE, ":setVolume(%d) [really applied when started]", this->volume_init );
		return WRET_OK;
	 }
	// validate gst status
	if ( this->gst_bin_play == NULL ) {
		Log(LOG_CRITICAL, ":setVolume(%d): NULL gst_bin_play", newVolume );
		return WRET_ERR_ILLEGAL_USE;
	 }

	gdouble vol = (gdouble)newVolume / 100.0;
	Log(LOG_INFO, ":setVolume(): %d (/100) [%g]", newVolume, vol );
	// convert the volume level [0,100] to GST range [0,10] and apply the new value
	g_object_set(G_OBJECT (this->gst_bin_play), "volume", vol, NULL);
	if ( this->playerListener != NULL )
		this->playerListener->changed_Volume( newVolume, isMuted(), this );
	return WRET_OK;
 }

WRESULT PlayerGStreamer010::setVolumeOffset( int volume_span ) {
	// validate gst status
	if ( this->gst_bin_play == NULL ) {
		Log(LOG_CRITICAL, ":setVolumeOffset(): NULL gst_bin_play" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	// get the current value
	gdouble vol = 0;
	g_object_get(G_OBJECT (this->gst_bin_play), "volume", &vol, NULL);
	if ( vol < 0 ) {
		Log(LOG_CRITICAL, ":setVolumeOffset(%d): FAILED getting current value", volume_span );
		return WRET_ERR_INTERNAL;
	 }
	// add (subtract) the (converted) span level to current level, fix the range
	vol += volume_span / 100.0;
	if ( vol < 0.0 )		vol = 0.0;
	else if ( vol > 1.0 )	vol = 1.0;
	Log(LOG_VERBOSE, ":setVolumeOffset(%d): %d (/100) [%g]", volume_span, (int)(vol*100.0), vol );
	// apply the new volume
	g_object_set(G_OBJECT (this->gst_bin_play), "volume", vol, NULL);
	if ( this->playerListener != NULL )
		this->playerListener->changed_Volume( (int)(vol*10.0), isMuted(), this );
	return WRET_OK;
 }

WRESULT PlayerGStreamer010::setMute( bool on ) {
	// validate gst status
	if ( this->gst_bin_play == NULL ) {
		Log(LOG_CRITICAL, ":setMute(): NULL gst_bin_play" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( on ) {
		// save the current volume to local var (used when un-muting)
		this->volume_muted = getVolume();
		Log(LOG_INFO, ":setMute(TRUE): saved volume %d (/100)", this->volume_muted );
		gdouble vol = 0.0;
		g_object_set(G_OBJECT (this->gst_bin_play), "volume", vol, NULL);
		if ( this->playerListener != NULL )
			this->playerListener->changed_Volume( this->volume_muted, true, this );
		return WRET_OK;
	 }
	else {
		WRESULT ret = setVolume(this->volume_muted);
		if ( WSUCCEEDED(ret) )
			Log(LOG_VERBOSE, ":setMute(FALSE): Restored volume %d (/100)", this->volume_muted );
		else
			Log(LOG_CRITICAL, ":setMute(FALSE): FAILED#%d restoring volume %d (/100)", ret, this->volume_muted );
		this->volume_muted = 0; // reset the shadow var
		if ( this->playerListener != NULL )
			this->playerListener->changed_Volume( this->volume_muted, false, this );
		return ret;
	 }

 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

int PlayerGStreamer010::getVolume() {
	// validate gst status
	if ( this->gst_bin_play == NULL ) {
		Log(LOG_CRITICAL, ":getVolume(): NULL gst_bin_play" );
		return -1;
	 }
	// get the GST volume, and convert to our range [0,100]
	gdouble vol = -1;
	g_object_get(G_OBJECT (this->gst_bin_play), "volume", &vol, NULL);
	if ( vol != -1 )
		return (int)(vol * 10.0);
	return -1;
 }

bool PlayerGStreamer010::isMuted() {
	// optimized, see setMute
	return (this->volume_muted != 0);
 }

////////////////////////////////////////////////////////////////////////////////////////////// VOLUME CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// POSITION CONTROL

WRESULT PlayerGStreamer010::setPosition( float position_rate ) {
	// validate gst status, input
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":setPosition(%f): NULL gst_pipeline", position_rate );
		return -1;
	 }
	if  ( position_rate < 0.0 || position_rate > 1.0 ) {
		Log(LOG_CRITICAL, ":setPosition(%f): Out of range [0,1]", position_rate );
		return -2;
	 }
	// get the length of current media
	gint64 len = 1;
	gint64 time_nanoseconds = 0;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( !gst_element_query_duration( this->gst_pipeline, &fmt, &len) ) {
		Log(LOG_CRITICAL, ":setPosition(%f): FAILED getting media length", position_rate );
		return WRET_ERR_ILLEGAL_USE;
	 }
	// calculate the new position and try to apply
	time_nanoseconds = position_rate * len;
	if ( !gst_element_seek(	this->gst_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
							GST_SEEK_TYPE_SET, time_nanoseconds,
							GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE) ) {
		Log(LOG_VERBOSE, ":setPosition(%f): FAILED Seeking [%"PRId64" ms]", position_rate, time_nanoseconds );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_INFO, ":setPosition(%f): Seek %"PRId64" ms", position_rate, time_nanoseconds );
	return WRET_OK;
 }

WRESULT PlayerGStreamer010::setPositionOffset( float position_rate_offset ) {
	// validate gst status, input
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":setPositionOffset(%f): NULL gst_pipeline", position_rate_offset );
		return -1;
	 }
	if  ( position_rate_offset < -1.0 || position_rate_offset > 1.0 ) {
		Log(LOG_CRITICAL, ":setPosition(%f): Out of range [-1,1]", position_rate_offset );
		return -2;
	 }
	// get the length of current media and its current position
	gint64 pos = 0;
	gint64 len = 1;
	gint64 time_nanoseconds = 0;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( !gst_element_query_duration( this->gst_pipeline, &fmt, &len ) ) {
		Log(LOG_CRITICAL, ":setPositionOffset(%f): FAILED getting media length", position_rate_offset );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( !gst_element_query_position( this->gst_pipeline, &fmt, &pos ) ) {
		Log(LOG_CRITICAL, ":setPositionOffset(%f): FAILED getting current position", position_rate_offset );
		return WRET_ERR_ILLEGAL_USE;
	 }
	// calculate the new position and try to apply
	time_nanoseconds = pos + position_rate_offset * len;
	if ( !gst_element_seek(	this->gst_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
							GST_SEEK_TYPE_SET, time_nanoseconds,
							GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE ) ) {
		Log(LOG_CRITICAL, ":setPositionOffset(%f): FAILED Seeking [%"PRId64" ms]", position_rate_offset, time_nanoseconds );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_INFO, ":setPositionOffset(%f): Seek %"PRId64" ms", position_rate_offset, time_nanoseconds );
	return WRET_OK;
 }

WRESULT PlayerGStreamer010::setPositionMs( unsigned long position_ms ) {
	// validate gst status, input
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":setPositionMs(%ld): NULL gst_pipeline", position_ms );
		return -1;
	 }
	if  ( position_ms < 0.0 ) {
		Log(LOG_CRITICAL, ":setPositionMs(%ld): Invalid negative value", position_ms );
		return -2;
	 }
	// calculate the new position and try to apply
	gint64 time_nanoseconds = position_ms * GST_MSECOND;
	if ( !gst_element_seek_simple( this->gst_pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, time_nanoseconds) ) {
		Log(LOG_VERBOSE, ":setPositionMs(%ld): FAILED Seeking", position_ms );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, ":setPositionMs(%ld): Seek %"PRId64" ms", position_ms, time_nanoseconds );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

float PlayerGStreamer010::getPosition() {
	// validate gst status
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":getPosition(): NULL gst_pipeline" );
		return -1.0;
	 }
	// get the length of current media and its current position
	gint64 pos = 0;
	gint64 len = 1;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( !gst_element_query_duration( this->gst_pipeline, &fmt, &len ) ) {
		Log(LOG_CRITICAL, ":getPosition(): FAILED getting media length" );
		return -2.0;
	 }
	if ( !gst_element_query_position( this->gst_pipeline, &fmt, &pos ) ) {
		Log(LOG_CRITICAL, ":getPosition(): FAILED getting current position" );
		return -3.0;
	 }
	// calculate and return the relative position
	return (float)pos / (float)len;
 }

unsigned long PlayerGStreamer010::getPositionMs() {
	// validate gst status
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":getPositionMs(): NULL gst_pipeline" );
		return -1;
	 }
	// get current position
	gint64 pos = 0;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( !gst_element_query_position( this->gst_pipeline, &fmt, &pos) ) {
		Log(LOG_CRITICAL, ":getPositionMs(): FAILED getting current position" );
		return -2;
	 }
	// convert nanoseconds to milliseconds
	return (unsigned long)(pos/GST_MSECOND);
 }

std::string PlayerGStreamer010::getPositionStr() {
	// validate gst status
	if ( this->gst_pipeline == NULL ) {
		Log(LOG_CRITICAL, ":getPositionMs(): NULL gst_pipeline" );
		return "";
	 }
	GstState state;
	GstStateChangeReturn ret = gst_element_get_state(this->gst_pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
	if ( ret == GST_STATE_CHANGE_FAILURE ) // can't load state!
		return "";
	if ( state != GST_STATE_PLAYING && state != GST_STATE_PAUSED )
		return "";
	// get current position
	gint64 pos = 0;
	GstFormat fmt = GST_FORMAT_TIME;
	if ( !gst_element_query_position( this->gst_pipeline, &fmt, &pos) ) {
		Log(LOG_CRITICAL, ":getPositionMs(): FAILED getting current position" );
		return "";
	 }
	// convert nanoseconds to milliseconds

	char position[256];
	memset(position, 0, 256);
	sprintf(position, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
	return string(position);
 }

//////////////////////////////////////////////////////////////////////////////////////////// POSITION CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void PlayerGStreamer010::setRepeatItem( bool value ) {
	this->currentRepeat = (short)value;
 }

void PlayerGStreamer010::setRepeatPlayList( bool value ) {
	this->playListRepeat = (short)value;
 }

WRESULT PlayerGStreamer010::setDeviceOutput( const std::string& dev_file ) {
	if ( !isThreadRunning() ) {
		// we will have to wait till thread is running to apply the new setting!
		// save the output (name) to local var
		this->device_output = dev_file;
		Log(LOG_VERBOSE, ":setDeviceOutput(%s) [really applied when started]", this->device_output.c_str() );
		return WRET_OK;
	 }
	else {
		Log(LOG_WARNING, ":setDeviceOutput(%s) [already started]", dev_file.c_str() );
	 }
	// validate current state: sink audio must be ready
	if ( this->gst_sink_audio == NULL ) {
		Log(LOG_CRITICAL, ":setDeviceOutput(%s): NULL gst_sink_audio [but Player running!?]", dev_file.c_str() );
		return WRET_ERR_INTERNAL;
	 }
	// validate current state: while playing, it is not allowed so will try to stop..
	if ( isPlaying() ) {
		Log(LOG_WARNING, ":setDeviceOutput(%s) : Set while playing.. Stopping..", dev_file.c_str() );
		WRESULT ret = stop();
		if ( WFAILED(ret) ) {
			Log(LOG_CRITICAL, ":setDeviceOutput(%s) : ERROR#%d while stopping playback!", dev_file.c_str(), ret );
			return WRET_ERR_ILLEGAL_USE;
		 }
	 }
	else Log(LOG_INFO, ":setDeviceOutput(%s) : Set while running.. trying to apply", dev_file.c_str() );
	// try to set the new device
	this->device_output = dev_file;
	g_object_set(G_OBJECT (this->gst_sink_audio), "device", this->device_output.c_str(), NULL);
	return WRET_OK;
 }


WRESULT PlayerGStreamer010::setDeviceDriver( const std::string& device_driver ) {
	if ( isThreadRunning() ) {
		Log(LOG_WARNING, ":setDeviceDriver(%s) [already started]", device_driver.c_str() );
		return WRET_ERR_ILLEGAL_USE;
	 }

	this->device_sink = device_driver;
	Log(LOG_INFO, ":setDeviceDriver(%s) [really applied when started]", this->device_sink.c_str() );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PlayerGStreamer010::getAPIInfo( std::map<std::string, std::string>& fields ) {
	// get/parse the Library information
	guint major, minor, micro, nano;
	gst_version (&major, &minor, &micro, &nano);
	fields["GS_Version"] = Utilities::format("%d.%d.%d", major, minor, micro);
	if (nano == 1)
		fields["GS_Built"] = "(CVS)";
	else if (nano == 2)
		fields["GS_Built"] = "(Prerelease)";
	else
		fields["GS_Built"] = "[unknown]";
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS

const char* PlayerGStreamer010::getGstState2String( GstState state ) {
	switch(state) {
		case GST_STATE_VOID_PENDING:	return "PENDING";
		case GST_STATE_NULL:			return "NULL";
		case GST_STATE_READY:			return "READY";
		case GST_STATE_PAUSED:			return "PAUSED";
		case GST_STATE_PLAYING:			return "PLAYING";
		default:						return "UNKNOWN";
	 }
 }

bool PlayerGStreamer010::waitGstState( const GstState* gst_state, GstState waiting_gst_state, unsigned long timeout_ms ) {
	// wait (timeout_ms) milliseconds untill state is applied
	// see gst_StateChanged
	if ( timeout_ms < 50 ) {
		if ( *gst_state == waiting_gst_state )
			return true;
		return false;
	 }
	unsigned int i = -1;
	timeout_ms = timeout_ms / 20; // sleep step is 20 milliseconds
	while ( ++i < timeout_ms ) {
		if ( *gst_state == waiting_gst_state )
			return true;
		ThreadImpl::sleepForMSec(20);
	 }
	return false;
 }

////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
